package top.jacktgq.concurrent_utils.reentrant.readwrite;

import top.jacktgq.utils.LogUtils;
import top.jacktgq.utils.Sleeper;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @Author CandyWall
 * @Date 2020/11/5--15:09
 * @Description 测试使用读写锁
 *                  1、对读操作加读锁，对写操作加写锁：
 *                      读读可以并发
 *                      读写互斥
 *                      写写互斥
 *                  2、读写锁重入时支持降级，但不支持升级
 *                      重入时升级：即持有读锁的情况下去获取写锁，会导致获取写锁永久等待。
 *                      重入时降级：即持有写锁的情况下可以获取读锁。
 */
public class ReentrantLockReadWriteLock {
    public static void main(String[] args) {
        demo1();
        //demo2();
        //demo3();
        //demo4();
    }

    //示例1：创建两个线程同时读取，
    //现象：读取的过程中可以同时进行，互不干扰
    private static void demo1() {
        DataContainer dataContainer = new DataContainer();
        new Thread(() -> {
            dataContainer.read();
        }, "t1").start();

        new Thread(() -> {
            dataContainer.read();
        }, "t2").start();
    }

    //示例2：创建两个线程一个读，一个写，读在前，写在后
    //现象：在读取操作完成之后，写操作才能继续完成；读的时候是不允许写的，会加锁
    private static void demo2() {
        DataContainer dataContainer = new DataContainer();
        new Thread(() -> {
            dataContainer.read();
        }, "t1").start();

        Sleeper.sleep(100, TimeUnit.MILLISECONDS);

        new Thread(() -> {
            dataContainer.write();
        }, "t2").start();
    }

    //示例3：创建两个线程一个读，一个写，写在前，读在后
    //现象：在写操作完成之后，读操作才能继续完成；写的时候是不允许读的，会加锁
    private static void demo3() {
        DataContainer dataContainer = new DataContainer();
        new Thread(() -> {
            dataContainer.write();
        }, "t1").start();

        Sleeper.sleep(100, TimeUnit.MILLISECONDS);

        new Thread(() -> {
            dataContainer.read();
        }, "t2").start();
    }

    //示例4：创建两个线程同时写
    //现象：在其中一个写操作完成之后，另一个线程才能继续写；多个线程写的时候会加锁
    private static void demo4() {
        DataContainer dataContainer = new DataContainer();
        new Thread(() -> {
            dataContainer.write();
        }, "t1").start();

        Sleeper.sleep(100, TimeUnit.MILLISECONDS);

        new Thread(() -> {
            dataContainer.write();
        }, "t2").start();
    }
}

class DataContainer {
    private Object data;
    private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock readLock = rw.readLock();   //读锁
    private ReentrantReadWriteLock.WriteLock writeLock = rw.writeLock();//写锁

    public Object read() {
        LogUtils.log("DataContainer", "获取读锁...");
        readLock.lock();
        try {
            LogUtils.log("DataContainer", "读取...");
            Sleeper.sleep(1);
            return data;
        } finally {
            LogUtils.log("DataContainer", "释放读锁...");
            readLock.unlock();
        }
    }

    public void write() {
        LogUtils.log("DataContainer", "获取写锁...");
        writeLock.lock();
        try {
            LogUtils.log("DataContainer", "写入...");
            Sleeper.sleep(1);
        } finally {
            LogUtils.log("DataContainer", "释放写锁...");
            writeLock.unlock();
        }
    }
}

//读写锁重入时支持降级，但不支持升级
class CachedData {
    Object data;
    // 是否有效，如果失效，需要重新计算 data
    volatile boolean cacheValid;
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    void processCachedData() {
        rwl.readLock().lock();
        if (!cacheValid) {
            // 获取写锁前必须释放读锁
            rwl.readLock().unlock();
            rwl.writeLock().lock();
            try {
                // 判断是否有其它线程已经获取了写锁、更新了缓存, 避免重复更新
                if (!cacheValid) {
                    data = "";
                    cacheValid = true;
                }
                // 降级为读锁, 释放写锁, 这样能够让其它线程读取缓存
                rwl.readLock().lock();
            } finally {
                rwl.writeLock().unlock();
            }
        }
        // 自己用完数据, 释放读锁
        try {
            use(data);
        } finally {
            rwl.readLock().unlock();
        }
    }

    private void use(Object o) {

    }
}